/** Communication protocoll 
 *  @author ChrM and Sun Microsystems */

package info.chrm.jtalkerserver;

import info.chrm.helper.*;
import java.net.*;
import java.io.*;

public class JTalkerProtocol {
    /** Constructor */
    public JTalkerProtocol (JTalkerServerThread _parent) {
       this.parent = _parent;
       this.myParser = new JTalkerParser();
    }

    /** Gets the logger */
    public Logger getLogger() {
       return this.parent.getLogger();
    }

    /** Protocoll main Loop */
    public String processInput(String _theInput) {
        String theOutput = null;
        boolean sendUpdateIndication = true;
	this.closeConnectionNow = false;

        if (state == WAITING) {
	    // New Client connected -> Send greetings
	    theOutput = "<JTALKER>";
            theOutput += "<" + JTalkerParser.MESSAGE + " content=\"Welcome on JTalker-Server " + this.parent.parent.RELEASE + "\" type=\"info\"/>";
	    theOutput += "</JTALKER>";
            state = LOGGING;
        } else if (state == LOGGING) {	    
	    // Waiting for user name -> parse -> send new User List
	    if(!this.myParser.parsing(_theInput)) {
	       theOutput = getProcessString(0);
	       state = LOGGING; 
	    } else {
	       // Is this an account creation?
	       if(this.myParser.isNewMember()) {
		  // create new account
		  this.getLogger().write("+ New Member creation procedure starts...");
		  if(this.myParser.getAMember(0) == null ||
		     !JTalkerServer.isAllowedUserName(this.myParser.getAMember(0).name) ||
		     !this.parent.parent.addGlobalUser(this.myParser.getAMember(0).name, 
					               this.myParser.getAMember(0).passwd)) {
		     theOutput = getProcessString(1);
		     this.getLogger().writeError("Can't create Member: " + this.myParser.getAMember(0).name);
		     state = LOGGING;   
		  } else {
		     //theOutput = "<JTALKER><error message=\"[INFO] Account successfull created. Welcome.\"/></JTALKER>";
		     theOutput = getProcessString(2);
		     state = LOGGING;   		     
		  }
		  
		  this.closeConnectionNow = true;
	       } else {
		  // Member Login
		  JTalkerMember tmpJTM = null;
		  JTalkerParser.OneMember tmpOM = this.myParser.getAMember(0);

		  if(tmpOM != null) {		     
		     tmpJTM = this.parent.parent.addMember(tmpOM.name, 
							   tmpOM.passwd,
							   this.parent.socket,
							   tmpOM.type,
							   tmpOM.sid);
		  }
		  
		  if(tmpJTM == null) {
		     theOutput = getProcessString(3); // Wrong Name or unregistered user
		     state = LOGGING;                    
		  } else {
		     theOutput = "<JTALKER>";

		     // Send Session ID after webclient login
		     if(tmpJTM.getUserType().equals(JTalkerMember.USER_TYPE_WEBCLIENT))
			theOutput += "<MESSAGE content=\"" + tmpJTM.getSessionID() + "\" type=\"sid\" />";
		     
		     theOutput += this.parent.parent.getXMLMemberList(this.parent.getMember());
		     theOutput += "</JTALKER>";
		     state = GET_PACKAGE;            
		  }	       
	       }
	    }
        } else if (state == GET_PACKAGE) {
	    // Getting some XML-Packages
            if (_theInput.startsWith("<JTALKER>") || _theInput.startsWith("<jtalker>")) {
	       // Start Parsing here
	       if(!this.myParser.parsing(_theInput)) {
		  theOutput = getProcessString(0);
		  state = GET_PACKAGE; 
	       } else {
		  if(this.myParser.getMemberStatus() != null) {
		     // Change Staus of current User
                     sendUpdateIndication = false;
		     this.parent.getMember().setUserStatus(this.myParser.getMemberStatus());
		  }
		  if(this.myParser.getRecs() != null) {
		     // Message was send -> Save this data to passed Member (by Recs)
		     for( int i = 0; i < this.myParser.getRecsAsArray().length; i++ ) { 
			JTalkerMember tmpMem = this.parent.parent.getMember(this.myParser.getRecsAsArray()[i]);		      

			if(tmpMem != null) {
                            // DEBUG
                            this.getLogger().write("+ Message from: " + 
			        this.parent.getMember().getUserName() + " to: " + tmpMem.getUserName() + " type: " + 
                                this.myParser.getMessageType());

			   String tmpMessage = this.myParser.getMessage();
			   
			   // make this HTML compatible
			   tmpMessage = toHTMLString(tmpMessage);
			   
			   if(!tmpMem.addMessage(tmpMessage, 
					     this.parent.getMember().getUserName(), 
					     this.myParser.getMessageType(),
					     this.myParser.getMessageSubject(),
                                             this.myParser.getMessageID())) {
			      
                              // Can't forward message (bigger problem)!
			      // TODO: Handle this much better (e.g. sending error message to the sender?)
			      this.getLogger().writeError("*** ERROR: Runtime - can't JTalkerMember.addMessage() from: " + 
				    this.parent.getMember().getUserName() + " to: " + tmpMem.getUserName());
			   }
			
			   // Redirect Chatroom Messages
                           // TODO: add welcome message for new
                           // chatroom users and call it again!
			   if(tmpMem.isChatroom())
			      this.parent.parent.redirectChatroomMessages();
			}
			else {
			   this.getLogger().writeError("*** Message with wrong receiver recieved, from: " + this.parent.getMember().getUserInfo());
			   // Add error message for the sender
                           this.parent.getMember().addMessage("Unknown user: " + this.myParser.getRecsAsArray()[i], 
                                            "@server", "error", null, null);
			}
		     }
		  }  
		  if(this.myParser.getCommand() != null) {
		     // No message in this package, but a command was recieved
		     String tmpRetVal = 
                         this.parent.parent.getPlugInLoader().invokePlugInMethod(
                                 this.myParser.getCommand(), 
                                 this.parent.getMember(), 
                                 this.myParser.getCommandID());

                     boolean sendError = false;

		     if(tmpRetVal == null) {
			tmpRetVal = this.parent.parent.getPlugInLoader().getLastError();			
                        sendError = true;
		     }
		     
		     // Add response message
		     this.parent.getMember().addMessage(		     
			   this.toHTMLString(tmpRetVal),
			   "@server",
			   "command_response",
			   null,
                           this.myParser.getCommandID());

                     if(sendError) {
                        // Add error message
                        this.parent.getMember().addMessage(		     
			    this.toHTMLString(tmpRetVal),
			    "@server",
			    "error",
			    null,
                            this.myParser.getCommandID());
                     }
		  }
		     
                  // Add update indication
                  /*if(sendUpdateIndication) {
                    this.parent.getMember().addMessage(		     
                       this.toHTMLString("Please send me your sign of life!"),
                       "@server",
                       "update",
                       null);
                  }*/
		  
		  // Standard post data: Send user List and all stored messages
		  theOutput = "<JTALKER>";
		  theOutput += this.parent.parent.getXMLMemberList(this.parent.getMember());	// + Messages for this user!!!
		  theOutput += getAllMessgesForUser();
		  theOutput += "</JTALKER>";
		  state = GET_PACKAGE; 		  
	       }
            } else {
               theOutput = getProcessString(0);	// Wrong protokoll
	       state = GET_PACKAGE;
            }        
        }
        return theOutput;
    }

    /** Generates a XML-formatted list with all messages for this user */
    private String getAllMessgesForUser() {
       String tmpRetVal = "";

       // Check if the user exists (or the session is closed)
       if(this.parent.getMember() == null) {
	 this.closeConnectionNow = true;
	 return getProcessString(4);
       }
       
       while(this.parent.getMember().numOfMsgs() > 0 ) { 
	 // List all messages
	 JTalkerMember.Message tmpMsg = this.parent.getMember().getMessage();
	 if(tmpMsg != null) {
	    tmpRetVal += "<" + JTalkerParser.MESSAGE + " content=\"";
	    tmpRetVal += (tmpMsg.content + "\"");
	    if(tmpMsg.subject != null)
	       tmpRetVal += (" subject=\"" + tmpMsg.subject + "\"");
	    tmpRetVal += (" sender=\"" + tmpMsg.sender +"\" type=\"" + tmpMsg.type + "\" id=\"" + tmpMsg.ID + "\"/>");			
	 }		     
       }

       return tmpRetVal;
    }

    /** Generates an unique error/info/process message. */
    public static String getProcessString(int _errno) {
       switch(_errno) {
	  case 0: 
	     return "<JTALKER><" + JTalkerParser.MESSAGE + " content=\"[ERROR] Wrong Protocol! Try again!\" type=\"error\"/></JTALKER>";
	  case 1:
	     return "<JTALKER><" + JTalkerParser.MESSAGE + " content=\"[ERROR] Can't create account - user exists or the name is not wellformed (only letters and numbers are allowed). Sorry.\" type=\"error\"/></JTALKER>";
	  case 2:
	     return "<JTALKER><" + JTalkerParser.MESSAGE + " content=\"[INFO] Account successfull created. Welcome.\" type=\"info\"/></JTALKER>";
	  case 3:
	     return "<JTALKER><" + JTalkerParser.MESSAGE + " content=\"[ERROR] Wrong name/passwd or unregistered user. Try again!\" type=\"error\"/></JTALKER>";
	  case 4:
	     return "<JTALKER><" + JTalkerParser.MESSAGE + " content=\"[ERROR] Session Closed.\" type=\"error\"/></JTALKER>";
	  case 5:
	     return "<JTALKER><" + JTalkerParser.MESSAGE + " content=\"[UPDATE] You have new messages.\" type=\"update\"/></JTALKER>";
	  default:
	     return "<JTALKER><" + JTalkerParser.MESSAGE + " content=\"Unspecified Server Message.\" type=\"info\"/></JTALKER>";
       }
    }

    /**
     * Returns the specified string converted to a format suitable for
     * HTML. All signle-quote, double-quote, greater-than, less-than and
     * ampersand characters are replaces with their corresponding HTML
     * Character Entity code.
     *
     * @param in the String to convert
     * @return the converted String
     */
    public static String toHTMLString(String in) {
      StringBuffer out = new StringBuffer();
      for (int i = 0; in != null && i < in.length(); i++) {
	 char c = in.charAt(i);
	 // ASCI Entitys
	 if (c == '\'') {
	    out.append("&#39;");
	 }
	 else if (c == '\"') {
	    out.append("&#34;");
	 }
	 else if (c == '<') {
	    out.append("&lt;");
	 }
	 else if (c == '>') {
	    out.append("&gt;");
	 }
	 else if (c == '&') {
	    out.append("&amp;");
	 }

	 // ISO 8859-1 (DE) Character Entities - incomplete, shoud be correct handled by the client (charset)!
	 else if (c == '\u00e4') {
	    out.append("&#228;");
	 }
	 else if (c == '\u00c4') {
	    out.append("&#196;");
	 }
	 else if (c == '\u00d6') {
	    out.append("&#214;");
	 }
	 else if (c == '\u00f6') {
	    out.append("&#246;");
	 }
	 else if (c == '\u00fc') {
	    out.append("&#252;");
	 }
	 else if (c == '\u00dc') {
	    out.append("&#220;");
	 }
	 else if (c == '\u00df') {
	    out.append("&#223;");
	 }
	 
	 // New Line killer
	 else if (c == '\n') {
	    out.append("_");
	 }
	 else if (c == '\r') {
	    out.append("_");
	 }
	 else {
	    out.append(c);
	 }
      }
      return out.toString();  
    }

    // Private members
    private static final int WAITING = 0;
    private static final int LOGGING = 1;
    private static final int GET_PACKAGE = 2;
    private static final int CREATING_ACCOUNT = 3;
    private int state = WAITING;
    private JTalkerServerThread parent;
    private JTalkerParser myParser;
    public boolean closeConnectionNow = false;

}
